package com.core.hibernate;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;

import com.core.hibernate.exceptionevent.ExceptionEvent;
import com.core.managers.SessionFactoryManager;
import com.core.protocols.IHibernateDelegate;
import com.core.protocols.IHibernateFetchRequestDelegate;
import com.core.protocols.IKVObject;
import com.core.utils.HQLConnection;
import com.core.utils.HibernateParameter;
import com.model.KeyValue;

public class HibernateFetchRequest implements IHibernateDelegate {

	private IHibernateFetchRequestDelegate delegate = null;
	private Class<?> entityClass = null;
	private String predicate = null;
	private String selectionCriteria = null;
	private Object userInfo = null;
	private Set<String> expandEntities = null;
	private Set<HQLConnection> expandEntitiesConnections = null;
	private static final String EXCEPTION_SESSION_IS_NULL = "Session is null";

	public Set<HQLConnection> getExpandEntitiesConnections() {

		return expandEntitiesConnections;
	}

	public void setExpandEntitiesConnections(Set<HQLConnection> expandEntitiesConnections) {

		this.expandEntitiesConnections = expandEntitiesConnections;
	}

	public Set<String> getExpandEntities() {

		return expandEntities;
	}

	public void setExpandEntities(Set<String> expandEntities) {

		this.expandEntities = expandEntities;
	}

	public void setUserInfo(Object userInfo) {

		this.userInfo = userInfo;
	}

	public Object userInfo() {

		return this.userInfo;
	}

	public Class<?> entityClass() {

		return this.entityClass;
	}

	public String predicate() {

		return predicate;
	}

	public String getSelectionCriteria() {

		return selectionCriteria;
	}

	public void setSelectionCriteria(String value) {

		this.selectionCriteria = value;
	}

	public IHibernateFetchRequestDelegate getDelegate() {

		return delegate;
	}

	public void setDelegate(IHibernateFetchRequestDelegate delegate) {

		this.delegate = delegate;
	}

	private String hqlDeleteScript() {

		String entityTableName = SessionFactoryManager.sharedInstance().annotatedClassTableName(entityClass);
		if (entityTableName == null) {
			return null;
		}

		StringBuilder sb = new StringBuilder();

		sb.append(String.format("delete from %s", entityTableName));
		if (predicate != null) {
			sb.append(String.format(" %s", predicate));

		}
		return sb.toString();

	}

	private void sendDataToDelegate(List<?> data, Exception exception) {

		if (delegate == null) {
			return;
		}

		delegate.hibernateFetchResponse(this, data, exception);

	}

	public void performDelete(Class<?> entityClass, String predicate) {

		Exception exception = null;

		this.entityClass = entityClass;
		this.predicate = predicate;

		Session session = SessionFactoryManager.sharedInstance().openSession();
		if (session == null) {
			exception = new Exception(EXCEPTION_SESSION_IS_NULL);
			this.sendDataToDelegate(null, exception);
			return;
		}

		try {

			String hqlDeleteScript = this.hqlDeleteScript();
			if (hqlDeleteScript == null) {

				exception = new Exception("Delete script is empty");
				this.sendDataToDelegate(null, exception);

			} else {

				Transaction transaction = session.beginTransaction();

				try {

					/*
					 * execute
					 */
					Query query = session.createQuery(hqlDeleteScript);

					query.executeUpdate();

					transaction.commit();

					query = null;

				} catch (RuntimeException rte) {
					ExceptionHandler.handleException(rte, new ExceptionEvent(ExceptionEvent.PERFORMDELETE));

					if (transaction != null) {
						if (transaction.isActive()) {
							try {
								transaction.rollback();
							} catch (Exception e) {
								ExceptionHandler.handleException(e, new ExceptionEvent(ExceptionEvent.PERFORMDELETE));
							}
						}
					}
					transaction = null;
				}
			}
		} finally {
			session.close();
			session = null;
			this.sendDataToDelegate(null, exception);
		}

	}

	public String hqlSelectScript() {

		String entityTableName = SessionFactoryManager.sharedInstance().annotatedClassTableName(entityClass);
		if (entityTableName == null) {
			return null;
		}

		boolean hasExpandedEntities = (this.expandEntities != null);
		if (hasExpandedEntities) {
			hasExpandedEntities &= (this.expandEntities.size() > 0);
		}

		boolean hasExpandedEntitiesConnections = (this.expandEntitiesConnections != null);
		if (hasExpandedEntitiesConnections) {
			hasExpandedEntitiesConnections &= (this.expandEntitiesConnections.size() > 0);
		}

		StringBuilder sb = new StringBuilder();

		if (this.selectionCriteria != null) {
			sb.append(String.format("select %s", this.selectionCriteria));
		} else {
			// if ( hasExpandedEntities ) {
			sb.append(String.format("select %s", entityTableName));
			// }
		}

		sb.append(String.format(" from %s as %s", entityTableName, entityTableName));

		if (hasExpandedEntities) {
			for (String expandedEntity : this.expandEntities) {
				sb.append(String.format(" left join %s.%s as %s ", entityTableName, expandedEntity, expandedEntity));
			}
		}

		if (hasExpandedEntitiesConnections) {
			for (HQLConnection connection : this.expandEntitiesConnections) {
				sb.append(String.format(" join %s as %s", connection.getEntity(), connection.getAlias()));
			}
			sb.append(" where ");
			for (HQLConnection expandedEntityConnection : this.expandEntitiesConnections) {
				sb.append(String.format(" %s", expandedEntityConnection.getConnection()));
			}
		}

		if (predicate != null) {
			sb.append(String.format(" %s", predicate));

		}

		return sb.toString();

	}

	public List<?> performFetch(String hqlSelectScript, List<?> entities) {

		SessionFactoryManager sessionFactoryManager = SessionFactoryManager.sharedInstance();
		if (sessionFactoryManager == null) {
			return null;
		}

		Exception exception = null;

		if (hqlSelectScript == null) {
			exception = new Exception("hqlSelectScript error");
			this.sendDataToDelegate(null, exception);
			return null;
		}

		Session session = sessionFactoryManager.openSession();
		if (session == null) {
			return null;
		}

		List<?> result = null;
		try {

			Transaction transaction = null;
			Query query = null;
			try {

				transaction = session.beginTransaction();

				query = session.createQuery(hqlSelectScript);

				result = query.list();

				transaction.commit();

			} catch (RuntimeException rtException) {
				ExceptionHandler.handleException(rtException, new ExceptionEvent(ExceptionEvent.PERFORMFETCH));

				if (transaction != null) {
					if (transaction.isActive()) {
						try {
							transaction.rollback();
						} catch (Exception e) {
							ExceptionHandler.handleException(e, new ExceptionEvent(ExceptionEvent.PERFORMFETCH));
						}
					}
				}

			} finally {

			}

		} finally {
			session.close();
		}

		return result;
	}

	private void applyParams(Query query, Set<HibernateParameter> params) {

		if (query == null) {
			return;
		}

		if (params == null) {
			return;
		}

		for (HibernateParameter param : params) {
			query.setParameter(param.getParamName(), param.getParamValue());
		}
	}

	public void performFetch(Class<?> entityClass, String predicate, Set<HibernateParameter> params) {

		this.entityClass = entityClass;
		this.predicate = predicate;

		SessionFactoryManager sessionFactoryManager = SessionFactoryManager.sharedInstance();
		if (sessionFactoryManager == null) {
			return;
		}

		Exception exception = null;

		String sbQuery = this.hqlSelectScript();

		if (sbQuery == null) {
			exception = new Exception("hqlSelectScript error");
			this.sendDataToDelegate(null, exception);
			return;
		}

		Session session = sessionFactoryManager.openSession();
		if (session == null) {
			return;
		}

		try {

			Transaction transaction = null;
			Query query = null;
			List<?> result = null;
			try {

				transaction = session.beginTransaction();

				query = session.createQuery(sbQuery);
				this.applyParams(query, params);

				result = query.list();

				transaction.commit();

			} catch (RuntimeException rtException) {
				ExceptionHandler.handleException(rtException, new ExceptionEvent(ExceptionEvent.PERFORMFETCH));

				if (transaction != null) {
					if (transaction.isActive()) {
						try {
							transaction.rollback();
						} catch (Exception e) {
							ExceptionHandler.handleException(e, new ExceptionEvent(ExceptionEvent.PERFORMFETCH));
						}
					}
				}

			} finally {
				this.sendDataToDelegate(result, exception);
			}

		} finally {
			session.close();
		}

	}

	public static IKVObject objectByUNID(Class<?> entityClass, Integer unid, Session externalSession) {

		IKVObject result = null;
		if (unid == null) {
			return result;
		}

		if (entityClass == null) {
			return result;
		}

		boolean hasExternalSession = (externalSession != null);

		Session session = null;
		if (!hasExternalSession) {
			session = SessionFactoryManager.sharedInstance().openSession();
		} else {
			session = externalSession;
		}

		try {

			result = (IKVObject) session.get(entityClass, unid);

		} catch (Exception e) {

			ExceptionHandler.handleException(e, new ExceptionEvent(ExceptionEvent.OBJECTBYUNID));
			result = null;

		} finally {
			if (!hasExternalSession) {
				session.close();
			}
		}

		return result;

	}

	public static IKVObject mergeEntityByIDWithTemplate(Integer id, IKVObject template) throws Exception {

		IKVObject result = null;
		if (template == null) {
			return result;
		}

		if (id == null) {
			return result;
		}

		Exception exception = null;
		Session session = SessionFactoryManager.sharedInstance().openSession();
		if (session == null) {
			exception = new Exception(EXCEPTION_SESSION_IS_NULL);
			throw exception;
		}

		try {
			Transaction transaction = session.beginTransaction();
			if (transaction == null) {
				exception = new Exception("Transaction is null");
				throw exception;
			}

			try {
				result = (IKVObject) session.get(template.getClass(), id);

				template.assignTo(result);

				session.merge(result);

				transaction.commit();

			} catch (RuntimeException runtimeException) {

				ExceptionHandler.handleException(runtimeException, new ExceptionEvent(ExceptionEvent.MERGEOBJECT));
				if (transaction != null) {
					if (transaction.isActive()) {
						try {
							transaction.rollback();
						} catch (Exception rollbackException) {
							ExceptionHandler.handleException(rollbackException, new ExceptionEvent(ExceptionEvent.MERGEOBJECT));
						}
					}
				}
			}

		} finally {
			session.close();
			session = null;
		}

		return result;
	}

	public static IKVObject setCustomPropertyValue(Class<?> objectClass, Integer id, KeyValue propertyValue) throws Exception {

		IKVObject result = null;
		if (objectClass == null) {
			return result;
		}

		if (id == null) {
			return result;
		}

		Exception exception = null;
		Session session = SessionFactoryManager.sharedInstance().openSession();
		if (session == null) {
			exception = new Exception(EXCEPTION_SESSION_IS_NULL);
			throw exception;
		}

		try {
			Transaction transaction = session.beginTransaction();
			if (transaction == null) {
				exception = new Exception("Transaction is null");
				throw exception;
			}

			try {
				result = (IKVObject) session.get(objectClass, id);

				/**
				 * reflection
				 */

				Class<?>[] parametersType = new Class[1];
				parametersType[0] = String.class;
				Method customMethod = objectClass.getMethod(propertyValue.getKey(), parametersType);
				customMethod.invoke(result, propertyValue.getValue());

				session.merge(result);

				transaction.commit();

			} catch (RuntimeException runtimeException) {

				ExceptionHandler.handleException(runtimeException, new ExceptionEvent(ExceptionEvent.CUSTOMPROPERTYVALUE));

				if (transaction != null) {
					if (transaction.isActive()) {
						try {
							transaction.rollback();
						} catch (Exception rollbackException) {
							ExceptionHandler.handleException(rollbackException, new ExceptionEvent(ExceptionEvent.CUSTOMPROPERTYVALUE));
						}
					}
				}
			}

		} finally {
			session.close();
			session = null;
		}

		return result;
	}

	public static IKVObject createEntityWithTemplate(IKVObject template, Session externalSession) throws Exception {

		IKVObject result = null;
		if (template == null) {
			return result;
		}

		Exception exception = null;

		final boolean hasExternalSession = (externalSession != null);
		Session session = null;
		if (hasExternalSession) {
			session = externalSession;
		} else {
			session = SessionFactoryManager.sharedInstance().openSession();
		}
		if (session == null) {
			exception = new Exception(EXCEPTION_SESSION_IS_NULL);
			throw exception;
		}

		try {
			Transaction transaction = session.beginTransaction();
			if (transaction == null) {
				exception = new Exception("Transaction is null");
				throw exception;
			}

			try {
				result = template.getClass().newInstance();

				template.assignTo(result);

				session.saveOrUpdate(result);

				transaction.commit();

			} catch (RuntimeException runtimeException) {

				ExceptionHandler.handleException(runtimeException, new ExceptionEvent(ExceptionEvent.TEMPLATEEVENT));

				if (transaction != null) {
					if (transaction.isActive()) {
						try {
							transaction.rollback();
						} catch (Exception rollbackException) {
							ExceptionHandler.handleException(rollbackException, new ExceptionEvent(ExceptionEvent.TEMPLATEEVENT));
						}
					}
				}
			}

		} finally {
			if (!hasExternalSession) {
				session.close();
				session = null;
			}
		}

		return result;
	}
}
